package com.conlin.learn.io;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.CompletionHandler;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 1、选择合适的缓冲区大小：根据您的应用场景和硬件配置，选择一个合适的缓冲区大小。通常，1KB 到 8KB 的缓冲区大小是一个较为合理的范围。过大的缓冲区可能会导致不必要的内存占用和性能下降。
 * <p>
 * 2、使用 BufferedOutputStream：BufferedOutputStream 是一个包装类，它可以自动管理缓冲区，减少频繁的 I/O 操作。您可以结合 BufferedOutputStream 和 FileOutputStream 来提高写入效率。
 *
 * 3、使用 FileChannel 和 ByteBuffer：FileChannel 提供了更底层的 I/O 操作，允许您直接操作字节缓冲区。结合 ByteBuffer 使用，可以在某些情况下提高性能，尤其是在处理大文件时。
 *
 * 4、考虑使用内存映射文件：对于非常大的文件，内存映射文件可以提供更好的性能，因为它允许您直接将文件映射到内存中，而不需要显式的读写操作。不过，使用内存映射文件时需要注意内存管理和文件系统的限制。
 *
 * 5、避免频繁的 GC：如果您发现垃圾回收对性能有明显影响，可以尝试调整 JVM 的垃圾回收策略，或者减少大对象的创建频率。
 *
 * 6、使用异步 I/O：对于高并发的应用场景，可以考虑使用异步 I/O 操作（如 AsynchronousFileChannel），以避免阻塞主线程并提高整体性能。
 */
public class CopyFileOptimizeTest {
    public static void main(String[] args) {
        // 1KB 到 8KB 的缓冲区大小是一个较为合理的范围
//        oe();

        // Buffered  ~拒绝访问。
//        bos();

        // FileChannel 高效地读取和写入文件，尤其是在处理大文件时
//        fc();

        // 异步读写 读取文件时发生错误: java.nio.channels.ClosedChannelException
        af();
    }


    private static void oe() {
        String path = Objects.requireNonNull(CopyFileTest.class.getClassLoader().getResource("files/cat.png")).getPath();
        String copyPath = Objects.requireNonNull(CopyFileTest.class.getClassLoader().getResource("files/")).getPath();
        try (FileInputStream fileInputStream = new FileInputStream(path);
             FileOutputStream fileOutputStream = new FileOutputStream(copyPath + "cat_copy.png")) {
            Instant start = Instant.now();
            byte[] bytes = new byte[1024 * 8];
            int b;
            int num = 0;
            while ((b = fileInputStream.read(bytes)) != -1) {
                // 先将数据读到bytes  然后一次写入bytes  减少对磁盘的io操作
                fileOutputStream.write(bytes, 0, b);
                num++;
            }
            System.out.println("写磁盘次数" + num);
            fileOutputStream.flush();

            Instant end = Instant.now();
            Duration duration = Duration.between(start, end);
            System.out.println("Execution time: " + duration.toMillis() + " ms");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private static void bos() {
        String path = "J:\\java_work\\files\\cat.png";
        String copyPath = "J:\\java_work\\files\\cat_1.png";

        String charset = "UTF-8"; // 指定字符编码

        Instant start = Instant.now();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), charset));
             BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(copyPath), charset))) {

            char[] buffer = new char[1024 * 8];
            int charsRead;

            while ((charsRead = reader.read(buffer)) != -1) {
                writer.write(buffer, 0, charsRead);
            }

            writer.flush();
            System.out.println("复制完成！");

        } catch (IOException e) {
            e.printStackTrace();
            System.err.println("文件读取或写入时发生错误: " + e.getMessage());
        }

        Instant end = Instant.now();
        Duration duration = Duration.between(start, end);
        System.out.println("Execution time: " + duration.toMillis() + " ms");

    }

    private static void fc() {
        String path = "J:\\java_work\\files\\cat.png";
        String copyPath = "J:\\java_work\\files\\cat_3.png";
        Instant start = Instant.now();
        try (
                FileChannel reader = FileChannel.open(Paths.get(path), StandardOpenOption.READ);
                FileChannel destChannel = FileChannel.open(Paths.get(copyPath), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {

            // 创建一个字节缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024 * 8);

            // 循环读取源文件并写入目标文件
            while (true) {
                // 重置缓冲区为写模式
                buffer.clear();

                // 从源通道读取数据到缓冲区
                int bytesRead = reader.read(buffer);

                // 如果读取到的字节数为 -1，表示到达文件末尾，退出循环
                if (bytesRead == -1) {
                    break;
                }

                // 切换缓冲区为读模式
                buffer.flip();

                // 将缓冲区中的数据写入目标通道
                destChannel.write(buffer);
            }

            System.out.println("文件复制完成！");
        } catch (IOException e) {
            e.printStackTrace();
        }

        Instant end = Instant.now();
        Duration duration = Duration.between(start, end);
        System.out.println("Execution time: " + duration.toMillis() + " ms");
    }

    private static final ExecutorService executor = Executors.newFixedThreadPool(10);
    private static void af(){
        // 源文件路径和目标文件路径
        Path sourcePath = Paths.get( "J:\\java_work\\files\\cat.png");
        Path destinationPath = Paths.get( "J:\\java_work\\files\\cat_6.png");

        Instant start = Instant.now();

        // 创建异步文件通道
        try (AsynchronousFileChannel sourceChannel = AsynchronousFileChannel.open(sourcePath, StandardOpenOption.READ);
             AsynchronousFileChannel destChannel = AsynchronousFileChannel.open(destinationPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {

            // 创建一个字节缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024 * 8);

            // 提交任务到线程池
            for (int i = 0; i < 10; i++) {
                int finalI = i;
                executor.submit(() -> {
                    // 递归读取和写入文件
                    readAndWriteFile(sourceChannel, destChannel, buffer, finalI * 1024 * 8, new CompletableFuture<Void>());
                });
            }

        } catch (IOException e) {
            e.printStackTrace();
            System.err.println("文件读取或写入时发生错误: " + e.getMessage());
        }

        Instant end = Instant.now();
        Duration duration = Duration.between(start, end);
        System.out.println("Execution time: " + duration.toMillis() + " ms");
    }


    // 递归函数：异步读取文件并写入另一个文件
    private static void readAndWriteFile(AsynchronousFileChannel sourceChannel, AsynchronousFileChannel destChannel,
                                         ByteBuffer buffer, long position, CompletableFuture<Void> future) {
        // 异步读取文件
        sourceChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer bytesRead, ByteBuffer attachment) {
                if (bytesRead == -1) {
                    // 到达文件末尾，完成操作
                    future.complete(null);
                    System.out.println("文件复制完成！");
                    return;
                }

                // 切换缓冲区为读模式
                attachment.flip();

                // 异步写入文件
                destChannel.write(attachment, position, attachment, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer bytesWritten, ByteBuffer attachment) {
                        // 重置缓冲区为写模式
                        attachment.clear();

                        // 继续读取下一个块
                        readAndWriteFile(sourceChannel, destChannel, attachment, position + bytesWritten, future);
                    }

                    @Override
                    public void failed(Throwable exc, ByteBuffer attachment) {
                        // 写入失败，记录错误并完成 Future
                        future.completeExceptionally(exc);
                        System.err.println("写入文件时发生错误: " + exc.getMessage());
                    }
                });
            }

            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                // 读取失败，记录错误并完成 Future
                future.completeExceptionally(exc);
                System.err.println("读取文件时发生错误: " + exc);
            }
        });
    }
}
